package org.folio.rest.impl;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.json.Json;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.cql2pgjson.CQL2PgJSON;
import org.folio.okapi.common.OkapiToken;
import org.folio.rest.RestVerticle;
import org.folio.rest.annotations.Validate;
import org.folio.rest.jaxrs.model.*;
import org.folio.rest.jaxrs.model.Error;
import org.folio.rest.jaxrs.resource.BookInStorages;
import org.folio.rest.persist.PgUtil;
import org.folio.rest.persist.PostgresClient;
import org.folio.rest.persist.cql.CQLWrapper;
import org.folio.rest.tools.messages.MessageConsts;
import org.folio.rest.tools.messages.Messages;
import org.folio.rest.tools.utils.TenantTool;
import org.folio.rest.tools.utils.ValidationHelper;

import javax.validation.constraints.Pattern;
import javax.ws.rs.core.Response;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static io.vertx.core.Future.succeededFuture;

public class BookInStoragesResourceImpl implements BookInStorages {
  private final Logger logger = LogManager.getLogger("mod-book-in-storage");
  private final Messages messages = Messages.getInstance();
  private static final String BOOKINSTORAGES_TABLE = "book_in_storage";
  private static final String LOCATION_PREFIX = "/bookInStorages/";

  @Override
  @Validate
  public void getBookInStorages(String query,
                                int offset, int limit, String lang,
                                Map<String, String> okapiHeaders,
                                Handler<AsyncResult<Response>> asyncResultHandler,
                                Context vertxContext) {

    try {
      logger.debug("Getting BookInStorages. {}+{} q={}", offset, limit, query);
      CQLWrapper cql = new CQLWrapper(new CQL2PgJSON(BOOKINSTORAGES_TABLE + ".jsonb"), query, limit, offset);
      PgUtil.postgresClient(vertxContext, okapiHeaders)
          .get(BOOKINSTORAGES_TABLE, BookInStorage.class, new String[]{"*"}, cql,
              true /*get count too*/, false /* set id */,
              reply -> {
                if (reply.failed()) {
                  ValidationHelper.handleError(reply.cause(), asyncResultHandler);
                  return;
                }
                BookInStoragesCollection notes = new BookInStoragesCollection();
                List<BookInStorage> results = reply.result().getResults();
                notes.setBookInStorages(results);
                Integer totalRecords = reply.result().getResultInfo().getTotalRecords();
                notes.setTotalRecords(totalRecords);
                asyncResultHandler.handle(succeededFuture(GetBookInStoragesResponse.respond200WithApplicationJson(notes)));
              });
    } catch (Exception e) {
      ValidationHelper.handleError(e, asyncResultHandler);
    }
  }

  @Override
  @Validate
  public void postBookInStorages(String lang,
                                 BookInStorage entity,
                                 Map<String, String> okapiHeaders,
                                 Handler<AsyncResult<Response>> asyncResultHandler,
                                 Context context) {
    logger.info("postBookInStorages");

    String tenantId = TenantTool.calculateTenantId(
        okapiHeaders.get(RestVerticle.OKAPI_HEADER_TENANT));
    String id = entity.getId();
    if (id == null || id.trim().length() == 0) {
      id = UUID.randomUUID().toString();
      entity.setId(id);
    }
    entity.setStatus(BookInStorage.Status.EDITING);
    PostgresClient.getInstance(context.owner(), tenantId).save(BOOKINSTORAGES_TABLE, id, entity,
        reply -> {
          if (reply.succeeded()) {
            String ret = reply.result();
            entity.setId(ret);
            asyncResultHandler.handle(succeededFuture(PostBookInStoragesResponse.
                respond201WithApplicationJson(entity, PostBookInStoragesResponse.headersFor201()
                    .withLocation(LOCATION_PREFIX + ret))));
          } else {
            handleError(reply.cause(), asyncResultHandler);
          }
        });
  }

  @Override
  @Validate
  public void getBookInStoragesById(String id,
                                    String lang, Map<String, String> okapiHeaders,
                                    Handler<AsyncResult<Response>> asyncResultHandler,
                                    Context vertxContext) {

    PgUtil.postgresClient(vertxContext, okapiHeaders).getById(BOOKINSTORAGES_TABLE, id, BookInStorage.class, reply -> {
      if (reply.failed()) {
        ValidationHelper.handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result() == null) {
        asyncResultHandler.handle(succeededFuture(GetBookInStoragesByIdResponse.respond404WithTextPlain(id)));
        return;
      }
      asyncResultHandler.handle(succeededFuture(GetBookInStoragesByIdResponse.respond200WithApplicationJson(reply.result())));
    });
  }

  @Override
  @Validate
  public void deleteBookInStoragesById(String id,
                                       String lang, Map<String, String> okapiHeaders,
                                       Handler<AsyncResult<Response>> asyncResultHandler,
                                       Context vertxContext) {

    PgUtil.postgresClient(vertxContext, okapiHeaders).delete(BOOKINSTORAGES_TABLE, id, reply -> {
      if (reply.failed()) {
        ValidationHelper.handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result().rowCount() != 1) {
        String message = messages.getMessage(lang, MessageConsts.DeletedCountError, 1, reply.result().rowCount());
        logger.error(message);
        asyncResultHandler.handle(succeededFuture(DeleteBookInStoragesByIdResponse
            .respond404WithTextPlain(message)));
        return;
      }
      asyncResultHandler.handle(succeededFuture(DeleteBookInStoragesByIdResponse.respond204()));
    });
  }

  @Override
  public void postBookInStoragesSubmitById(String id, String lang, Map<String, String> okapiHeaders,
                                           Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
    PgUtil.postgresClient(vertxContext, okapiHeaders).getById(BOOKINSTORAGES_TABLE, id, BookInStorage.class, reply -> {
      if (reply.failed()) {
        ValidationHelper.handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result() == null) {
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }
      BookInStorage bookInStorage = reply.result();
      if (bookInStorage.getStatus() != BookInStorage.Status.EDITING) {
        logger.error("不能送审状态为{}馆藏入库单{}}", bookInStorage.getStatus(), id);
        Errors valErr = ValidationHelper.createValidationErrorMessage("id", id,
            "不能送审该ID的馆藏入库单！");

        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }

      bookInStorage.setStatus(BookInStorage.Status.SUBMITED);
      updateMetadata(okapiHeaders, bookInStorage.getMetadata());

      PgUtil.postgresClient(vertxContext, okapiHeaders).update(BOOKINSTORAGES_TABLE, bookInStorage, id, reply1 -> {
        if (reply1.failed()) {
          handleError(reply.cause(), asyncResultHandler);
          return;
        }
        if (reply1.result().rowCount() == 0) {
          asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse.respond404WithTextPlain(id)));
          return;
        }
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond201WithApplicationJson(bookInStorage)));
      });
    });
  }

  @Override
  public void postBookInStoragesUnSubmitById(String id, String lang, Map<String, String> okapiHeaders,
                                             Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
    PgUtil.postgresClient(vertxContext, okapiHeaders).getById(BOOKINSTORAGES_TABLE, id, BookInStorage.class, reply -> {
      if (reply.failed()) {
        ValidationHelper.handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result() == null) {
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }
      BookInStorage bookInStorage = reply.result();
      if (bookInStorage.getStatus() != BookInStorage.Status.SUBMITED) {
        logger.error("不能退回状态为{}馆藏入库单{}}", bookInStorage.getStatus(), id);
        Errors valErr = ValidationHelper.createValidationErrorMessage("id", id,
            "不能退回该ID的馆藏入库单！");

        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }

      bookInStorage.setStatus(BookInStorage.Status.UN_SUBMIT);
      updateMetadata(okapiHeaders, bookInStorage.getMetadata());

      PgUtil.postgresClient(vertxContext, okapiHeaders).update(BOOKINSTORAGES_TABLE, bookInStorage, id, reply1 -> {
        if (reply1.failed()) {
          handleError(reply.cause(), asyncResultHandler);
          return;
        }
        if (reply1.result().rowCount() == 0) {
          asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse.respond404WithTextPlain(id)));
          return;
        }
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond201WithApplicationJson(bookInStorage)));
      });
    });
  }

  @Override
  public void postBookInStoragesReviewById(String id, String lang, Map<String, String> okapiHeaders,
                                           Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
    PgUtil.postgresClient(vertxContext, okapiHeaders).getById(BOOKINSTORAGES_TABLE, id, BookInStorage.class, reply -> {
      if (reply.failed()) {
        ValidationHelper.handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result() == null) {
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }
      BookInStorage bookInStorage = reply.result();
      if (bookInStorage.getStatus() != BookInStorage.Status.SUBMITED) {
        logger.error("不能审核状态为{}馆藏入库单{}", bookInStorage.getStatus(), id);
        Errors valErr = ValidationHelper.createValidationErrorMessage("id", id,
            "不能审核该ID的馆藏入库单！");

        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }

      bookInStorage.setStatus(BookInStorage.Status.REVIEWED);
      bookInStorage.setReviewBy(getUserName(okapiHeaders));
      bookInStorage.setReviewDate(new Date());
      updateMetadata(okapiHeaders, bookInStorage.getMetadata());

      PgUtil.postgresClient(vertxContext, okapiHeaders).update(BOOKINSTORAGES_TABLE, bookInStorage, id, reply1 -> {
        if (reply1.failed()) {
          handleError(reply.cause(), asyncResultHandler);
          return;
        }
        if (reply1.result().rowCount() == 0) {
          asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse.respond404WithTextPlain(id)));
          return;
        }
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond201WithApplicationJson(bookInStorage)));
      });
    });
  }

  @Override
  public void postBookInStoragesSignById(String id, String lang, Map<String, String> okapiHeaders,
                                         Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
    PgUtil.postgresClient(vertxContext, okapiHeaders).getById(BOOKINSTORAGES_TABLE, id, BookInStorage.class, reply -> {
      if (reply.failed()) {
        ValidationHelper.handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result() == null) {
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }
      BookInStorage bookInStorage = reply.result();
      if (bookInStorage.getStatus() != BookInStorage.Status.SUBMITED) {
        logger.error("不能将状态为{}馆藏入库单{}签收", bookInStorage.getStatus(), id);
        Errors valErr = ValidationHelper.createValidationErrorMessage("id", id,
            "不能签收该ID的馆藏入库单！");

        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }

      bookInStorage.setStatus(BookInStorage.Status.SIGNED);
      bookInStorage.setSignBy(getUserName(okapiHeaders));
      bookInStorage.setSignDate(new Date());
      updateMetadata(okapiHeaders, bookInStorage.getMetadata());

      PgUtil.postgresClient(vertxContext, okapiHeaders).update(BOOKINSTORAGES_TABLE, bookInStorage, id, reply1 -> {
        if (reply1.failed()) {
          handleError(reply.cause(), asyncResultHandler);
          return;
        }
        if (reply1.result().rowCount() == 0) {
          asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse.respond404WithTextPlain(id)));
          return;
        }
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond201WithApplicationJson(bookInStorage)));
      });
    });
  }

  @Override
  public void postBookInStoragesConfirmById(String id, String lang, Map<String, String> okapiHeaders,
                                            Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
    PgUtil.postgresClient(vertxContext, okapiHeaders).getById(BOOKINSTORAGES_TABLE, id, BookInStorage.class, reply -> {
      if (reply.failed()) {
        ValidationHelper.handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result() == null) {
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }
      BookInStorage bookInStorage = reply.result();
      if (bookInStorage.getStatus() != BookInStorage.Status.REVIEWED) {
        logger.error("不能确认状态为{}馆藏入库单{}", bookInStorage.getStatus(), id);
        Errors valErr = ValidationHelper.createValidationErrorMessage("id", id,
            "不能确认该ID的馆藏入库单！");

        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond404WithTextPlain(id)));
        return;
      }

      bookInStorage.setStatus(BookInStorage.Status.CONFIRMED);
      bookInStorage.setSignBy(getUserName(okapiHeaders));
      bookInStorage.setSignDate(new Date());
      updateMetadata(okapiHeaders, bookInStorage.getMetadata());

      PgUtil.postgresClient(vertxContext, okapiHeaders).update(BOOKINSTORAGES_TABLE, bookInStorage, id, reply1 -> {
        if (reply1.failed()) {
          handleError(reply.cause(), asyncResultHandler);
          return;
        }
        if (reply1.result().rowCount() == 0) {
          asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse.respond404WithTextPlain(id)));
          return;
        }
        asyncResultHandler.handle(succeededFuture(PostBookInStoragesSubmitByIdResponse.respond201WithApplicationJson(bookInStorage)));
      });
    });

  }


  @Override
  @Validate
  public void putBookInStoragesById(String id,
                                    String lang, BookInStorage entity, Map<String, String> okapiHeaders,
                                    Handler<AsyncResult<Response>> asyncResultHandler,
                                    Context vertxContext) {
    if (logger.isInfoEnabled()) {
      logger.info("PUT BookInStorage {} {}", id, Json.encode(entity));
    }

    String noteId = entity.getId();
    if (noteId != null && !noteId.equals(id)) {
      logger.error("Trying to change BookInStorage Id from {} to {}", id, noteId);
      Errors valErr = ValidationHelper.createValidationErrorMessage("id", noteId,
          "Can not change the id");
      asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse
          .respond422WithApplicationJson(valErr)));
      return;
    }

    updateMetadata(okapiHeaders, entity.getMetadata());

    PgUtil.postgresClient(vertxContext, okapiHeaders).update(BOOKINSTORAGES_TABLE, entity, id, reply -> {
      if (reply.failed()) {
        handleError(reply.cause(), asyncResultHandler);
        return;
      }
      if (reply.result().rowCount() == 0) {
        asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse.respond404WithTextPlain(id)));
        return;
      }
      asyncResultHandler.handle(succeededFuture(PutBookInStoragesByIdResponse.respond204()));
    });
  }

  static String extractFromSingleQuotes(String s) {
    int start = s.indexOf('\'');
    int end = s.lastIndexOf('\'');
    if (start >= end) {
      return s;
    }
    return s.substring(start + 1, end);
  }

  void handleError(Throwable throwable, Handler<AsyncResult<Response>> asyncResultHandler) {
    try {
      ValidationHelper.handleError(throwable, reply -> {
        Response response = reply.result();
        if (response != null && response.getEntity() instanceof Errors) {
          Errors errors = (Errors) response.getEntity();
          Error error = errors.getErrors().get(0);
          if (error.getMessage().contains("duplicate key value violates unique constraint")) {
            String fieldname = error.getParameters().get(0).getKey();
            fieldname = extractFromSingleQuotes(fieldname);
            String value = error.getParameters().get(0).getValue();
            error.setMessage("BookInStorage with " + fieldname + " '" + value + "' already exists");
          }
        }
        asyncResultHandler.handle(reply);
      });
    } catch (Exception e) {
      logger.error(throwable.getMessage(), throwable);
      logger.error(e.getMessage(), e);
      ValidationHelper.handleError(e, asyncResultHandler);
    }
  }

  public static String getUserName(Map<String, String> okapiHeaders) {
    String userName = null;
    try {
      userName = new OkapiToken(okapiHeaders.get(RestVerticle.OKAPI_HEADER_TOKEN)).getUsernameWithoutValidation();
    } catch (Exception e) {
      // ignore, userId remains null
    }
    return userName;
  }

  public static void updateMetadata(Map<String, String> okapiHeaders, Metadata md) {
    String userId = okapiHeaders.get(RestVerticle.OKAPI_USERID_HEADER);
    if (userId == null) {
      try {
        userId = new OkapiToken(okapiHeaders.get(RestVerticle.OKAPI_HEADER_TOKEN)).getUserIdWithoutValidation();
      } catch (Exception e) {
        // ignore, userId remains null
      }
    }
    md.setUpdatedDate(new Date());
    md.setUpdatedByUserId(userId);
  }
}
